Transformação na tabela ratings
%load_ext pretty_jupyter
Importar Bibliotecas¶
import pandas as pd
import os
import numpy as np
import re
from sklearn.pipeline import Pipeline
from sklearn.base import BaseEstimator, TransformerMixin
Carregar Arquivo¶
ratings_treino = pd.read_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/2.Datasets_Limpeza/ratings_treino.pickle", compression="gzip")
ratings_teste = pd.read_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/2.Datasets_Limpeza/ratings_teste.pickle", compression="gzip")
ratings_treino
| userId | movieId | rating | timestamp | |
|---|---|---|---|---|
| 213 | 5 | 47 | 5.0 | 1029389303 |
| 214 | 5 | 175 | 4.0 | 1029389417 |
| 215 | 5 | 257 | 4.0 | 1029389115 |
| 216 | 5 | 318 | 4.0 | 1029389280 |
| 217 | 5 | 319 | 4.0 | 1029389327 |
| ... | ... | ... | ... | ... |
| 33830996 | 330963 | 53953 | 0.5 | 1230144729 |
| 33830997 | 330963 | 54190 | 3.0 | 1230144915 |
| 33830998 | 330963 | 55069 | 5.0 | 1230144786 |
| 33830999 | 330963 | 55282 | 5.0 | 1230144757 |
| 33831000 | 330963 | 58293 | 0.5 | 1230144558 |
820508 rows × 4 columns
Classe¶
Classe 1: Eliminar a coluna "timestamp"¶
class EliminarColuna(BaseEstimator, TransformerMixin):
def __init__(self, col_eliminar):
self.col_eliminar = col_eliminar
def fit(self, X, y=None):
return self
def transform(self, X):
X_transformado = X.drop(columns=self.col_eliminar)
return X_transformado
Classe 2: Criar uma coluna com o total de avaliações por filme¶
class AvaliacoesFilme(BaseEstimator, TransformerMixin):
''' Classe que cria uma coluna com o total de avaliações por filme
Args:
- coluna: nome da coluna com o número de ocorrências de cada valor (movieId)
- X : nome da tabela (com dados de treino ou teste)
'''
def __init__(self, coluna):
self.coluna = coluna
def fit(self, X, y=None):
return self
def transform(self, X):
tabela_agrupada = X.groupby(self.coluna).size().reset_index(name="Numero_de_Avaliacoes_por_Filme")
tabela_mesclada = pd.merge(tabela_agrupada, X, on=self.coluna)
return tabela_mesclada
Classe 3: Criar uma coluna com o total de avaliações por usuário¶
class AvaliacoesUsuarios(BaseEstimator, TransformerMixin):
''' Classe que cria uma coluna com o total de avaliações por usuários
Args:
- coluna: nome da coluna com o número de ocorrências de cada valor (userId)
- X : nome da tabela (com dados de treino ou teste)
'''
def __init__(self, coluna):
self.coluna = coluna
def fit(self, X, y=None):
return self
def transform(self, X):
tabela_agrupada = X.groupby(self.coluna).size().reset_index(name="Numero_de_Avaliacoes_por_usuarios")
tabela_mesclada = pd.merge(tabela_agrupada, X, on=self.coluna)
return tabela_mesclada
Classse 4: Criar coluna rating_medio (simples)¶
class MediaSimples(BaseEstimator, TransformerMixin):
def __init__(self, col_movie, col_rating):
self.col_movie = col_movie
self.col_rating = col_rating
def fit(self, X, y=None):
return self
def transform(self, X):
df_media = X.groupby(self.col_movie)[self.col_rating].mean().reset_index()
df_media1 = df_media.rename(columns={self.col_rating:'rating_medio_simples'})
df_media_mesclado = pd.merge(X, df_media1, on=self.col_movie)
return df_media_mesclado
Classe 5: Criar Coluna rating_medio_ponderado¶
Faremos uma adaptação da fórmula da média ponderada com o objetivo de penalizar os filmes com baixo número de avaliações. A fórmula utilizada é a seguinte:
onde,
- Vamos somar log() por 1 para evitar erros com valores de log(0)
Quanto maior o fator de 𝑃𝑒𝑛𝑎𝑙𝑖𝑧𝑎cao, menor será a média dos filmes com pouca avaliação.
Aplicar o logaritmo natural é uma forma de deixar a distribuição das médias menos assimétricas e mais próximas da distribuição normal. O que reduz o impacto de valores extremos na média ponderada.
class MediaPonderada(BaseEstimator, TransformerMixin):
''' Classe que cria uma coluna com o rating médio ponderado '''
def __init__(self, col_avaliacoes_filmes , col_rating_medio_simples, penalizacao):
self.col_avaliacoes_filmes = col_avaliacoes_filmes
self.col_rating_medio_simples = col_rating_medio_simples
self.penalizacao = penalizacao
def fit(self, X, y=None):
return self
def transform(self, X):
fator_suavizacao = self.penalizacao
X['log_numero_avaliacoes'] = np.log(X[self.col_avaliacoes_filmes]+1)
X['rating_medio_ponderado'] = (X[self.col_avaliacoes_filmes]*X[self.col_rating_medio_simples]) / \
(X[self.col_avaliacoes_filmes] + X['log_numero_avaliacoes'] * fator_suavizacao)
X.drop(columns='log_numero_avaliacoes', inplace=True)
return X
Classe 6: Criar classe com média individual vs data¶
Esta classe serve para darmos um peso maior as avalições mais recentes. Isso é importante, pois elas refletem a opinião atual do usuário.
Para calcular do peso dado as avaliações em relação ao tempo, vamos utilizar uma função exponencial. Pois dessa forma, daremos um peso que vai aumentar/diminuir de maneira suave, sem mudanças abruptas no peso.
A função abaixo vai realizar duas contas:
$\text{Peso}= e^{-\text{taxa de decaimento}\times\text{diferença em dias}}$
$\text{Rating_Times} = \text{Rating} \times \text{Peso}$
Onde,
$\text{taxa de decaimento}$:controla a taxa de decaimento exponencial. Neste caso, foi definido como 0,0005.
$\text{diferença em dias}$: é a diferença em dias entre a data da avaliação e a data atual.
$\text{Rating}$: nota (avaliação) individual
class MediaTimes(BaseEstimator, TransformerMixin):
def __init__(self, current_date=None, decay_rate=0.0005):
''' Inicializando da Classe MediaTimes
Args:
- current_date: data atual
- decay_rate: a taxa de decaimento exponencial (default é 0.0005).
'''
self.current_date = current_date if current_date else pd.to_datetime('today')
self.decay_rate = decay_rate
def fit(self, X, y=None):
# Neste caso, fit não faz nada, pois não há parâmetros a aprender
return self
def transform(self, X, y=None):
''' Função que aplica a transformação em X
Args:
- X: DataFrame e contém as colunas 'rating' e 'timestamp'
'''
# Assumindo que X é um
X = X.copy()
# Converter timestamp para datetime
X['timestamp'] = pd.to_datetime(X['timestamp'], unit='s')
# Criar a nova coluna de rating ponderado
X['rating_times'] = X.apply(lambda row: self.calculate_weighted_rating(row['rating'], row['timestamp']), axis=1)
return X
def calculate_weighted_rating(self, rating, date):
''' Função que calcula Calcula o rating ponderado
com base na diferença em dias entre a data da avaliação
e a data atual. Ou seja,
- peso = e^(decay_rate * diferença dias)
- Rating_Times = Rating * Peso
'''
# Calculando a diferença em dias
days_diff = (self.current_date - date).days
# Calculando o peso
peso = np.exp(-self.decay_rate * days_diff)
# Retornando o rating ponderado
return rating * peso
Transformação em ratings¶
Criar Pipeline para Análise Exploratória¶
# Criar a pipeline
pipeline_analise = Pipeline([
('eliminar_timestamp',EliminarColuna(col_eliminar='timestamp')),
('avaliacoes_filme', AvaliacoesFilme(coluna='movieId')),
('avaliacoes_usuarios', AvaliacoesUsuarios(coluna='userId')),
('media_simples', MediaSimples(col_movie='movieId', col_rating='rating')),
('media_ponderada', MediaPonderada(col_avaliacoes_filmes='Numero_de_Avaliacoes_por_Filme',
col_rating_medio_simples='rating_medio_simples', penalizacao=50))
])
pipeline_analise
Pipeline(steps=[('eliminar_timestamp',
EliminarColuna(col_eliminar='timestamp')),
('avaliacoes_filme', AvaliacoesFilme(coluna='movieId')),
('avaliacoes_usuarios', AvaliacoesUsuarios(coluna='userId')),
('media_simples',
MediaSimples(col_movie='movieId', col_rating='rating')),
('media_ponderada',
MediaPonderada(col_avaliacoes_filmes='Numero_de_Avaliacoes_por_Filme',
col_rating_medio_simples='rating_medio_simples',
penalizacao=50))])In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Pipeline(steps=[('eliminar_timestamp',
EliminarColuna(col_eliminar='timestamp')),
('avaliacoes_filme', AvaliacoesFilme(coluna='movieId')),
('avaliacoes_usuarios', AvaliacoesUsuarios(coluna='userId')),
('media_simples',
MediaSimples(col_movie='movieId', col_rating='rating')),
('media_ponderada',
MediaPonderada(col_avaliacoes_filmes='Numero_de_Avaliacoes_por_Filme',
col_rating_medio_simples='rating_medio_simples',
penalizacao=50))])EliminarColuna(col_eliminar='timestamp')
AvaliacoesFilme(coluna='movieId')
AvaliacoesUsuarios(coluna='userId')
MediaSimples(col_movie='movieId', col_rating='rating')
MediaPonderada(col_avaliacoes_filmes='Numero_de_Avaliacoes_por_Filme',
col_rating_medio_simples='rating_medio_simples', penalizacao=50)Criar Pipeline para Modelagem¶
pipeline_modelagem = Pipeline([
('rating vs times', MediaTimes()),
('avaliacoes_filme', AvaliacoesFilme(coluna='movieId')),
('avaliacoes_usuarios', AvaliacoesUsuarios(coluna='userId')),
('media_simples', MediaSimples(col_movie='movieId', col_rating='rating')),
('media_ponderada', MediaPonderada(col_avaliacoes_filmes='Numero_de_Avaliacoes_por_Filme',
col_rating_medio_simples='rating_medio_simples', penalizacao=50))
])
pipeline_modelagem
Pipeline(steps=[('rating vs times',
MediaTimes(current_date=Timestamp('2024-07-04 17:11:16.318840'))),
('avaliacoes_filme', AvaliacoesFilme(coluna='movieId')),
('avaliacoes_usuarios', AvaliacoesUsuarios(coluna='userId')),
('media_simples',
MediaSimples(col_movie='movieId', col_rating='rating')),
('media_ponderada',
MediaPonderada(col_avaliacoes_filmes='Numero_de_Avaliacoes_por_Filme',
col_rating_medio_simples='rating_medio_simples',
penalizacao=50))])In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Pipeline(steps=[('rating vs times',
MediaTimes(current_date=Timestamp('2024-07-04 17:11:16.318840'))),
('avaliacoes_filme', AvaliacoesFilme(coluna='movieId')),
('avaliacoes_usuarios', AvaliacoesUsuarios(coluna='userId')),
('media_simples',
MediaSimples(col_movie='movieId', col_rating='rating')),
('media_ponderada',
MediaPonderada(col_avaliacoes_filmes='Numero_de_Avaliacoes_por_Filme',
col_rating_medio_simples='rating_medio_simples',
penalizacao=50))])MediaTimes(current_date=Timestamp('2024-07-04 17:11:16.318840'))AvaliacoesFilme(coluna='movieId')
AvaliacoesUsuarios(coluna='userId')
MediaSimples(col_movie='movieId', col_rating='rating')
MediaPonderada(col_avaliacoes_filmes='Numero_de_Avaliacoes_por_Filme',
col_rating_medio_simples='rating_medio_simples', penalizacao=50)Aplicar Pipeline nos dados de treino e teste¶
Pipeline para Análise Exploratória¶
# Usar a pipeline
ratings_treino_transformado = pipeline_analise.fit_transform(ratings_treino)
ratings_teste_transformado = pipeline_analise.transform(ratings_teste)
ratings_treino_transformado
| userId | Numero_de_Avaliacoes_por_usuarios | movieId | Numero_de_Avaliacoes_por_Filme | rating | rating_medio_simples | rating_medio_ponderado | |
|---|---|---|---|---|---|---|---|
| 0 | 5 | 43 | 47 | 1567 | 5.0 | 4.057754 | 3.286254 |
| 1 | 5 | 43 | 175 | 140 | 4.0 | 3.482143 | 1.258266 |
| 2 | 5 | 43 | 257 | 104 | 4.0 | 3.341346 | 1.032082 |
| 3 | 5 | 43 | 318 | 2948 | 4.0 | 4.415366 | 3.888469 |
| 4 | 5 | 43 | 319 | 207 | 4.0 | 3.910628 | 1.708250 |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 820503 | 330963 | 34 | 53953 | 126 | 0.5 | 3.361111 | 1.150161 |
| 820504 | 330963 | 34 | 54190 | 74 | 3.0 | 3.371622 | 0.860718 |
| 820505 | 330963 | 34 | 55069 | 28 | 5.0 | 3.875000 | 0.552543 |
| 820506 | 330963 | 34 | 55282 | 77 | 5.0 | 3.298701 | 0.861498 |
| 820507 | 330963 | 34 | 58293 | 91 | 0.5 | 2.659341 | 0.763192 |
820508 rows × 7 columns
ratings_teste_transformado
| userId | Numero_de_Avaliacoes_por_usuarios | movieId | Numero_de_Avaliacoes_por_Filme | rating | rating_medio_simples | rating_medio_ponderado | |
|---|---|---|---|---|---|---|---|
| 0 | 128 | 19 | 168 | 81 | 3.0 | 3.166667 | 0.851209 |
| 1 | 128 | 19 | 208 | 196 | 1.0 | 2.984694 | 1.271296 |
| 2 | 128 | 19 | 356 | 682 | 4.0 | 4.016862 | 2.716883 |
| 3 | 128 | 19 | 480 | 483 | 2.0 | 3.628364 | 2.212461 |
| 4 | 128 | 19 | 590 | 304 | 2.0 | 3.822368 | 1.969439 |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 207989 | 330948 | 218 | 115210 | 37 | 1.0 | 3.891892 | 0.657897 |
| 207990 | 330948 | 218 | 129779 | 2 | 1.5 | 2.750000 | 0.096609 |
| 207991 | 330948 | 218 | 130634 | 26 | 0.5 | 3.288462 | 0.448132 |
| 207992 | 330948 | 218 | 132584 | 1 | 0.5 | 0.500000 | 0.014022 |
| 207993 | 330948 | 218 | 136459 | 1 | 1.5 | 1.500000 | 0.042067 |
207994 rows × 7 columns
Pipeline para Modelagem¶
# Usar a pipeline
ratings_treino_transformado_modelagem = pipeline_modelagem.fit_transform(ratings_treino)
ratings_teste_transformado_modelagem = pipeline_modelagem.transform(ratings_teste)
ratings_treino_transformado_modelagem
| userId | Numero_de_Avaliacoes_por_usuarios | movieId | Numero_de_Avaliacoes_por_Filme | rating | timestamp | rating_times | rating_medio_simples | rating_medio_ponderado | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 5 | 43 | 47 | 1567 | 5.0 | 2002-08-15 05:28:23 | 0.091853 | 4.057754 | 3.286254 |
| 1 | 5 | 43 | 175 | 140 | 4.0 | 2002-08-15 05:30:17 | 0.073483 | 3.482143 | 1.258266 |
| 2 | 5 | 43 | 257 | 104 | 4.0 | 2002-08-15 05:25:15 | 0.073483 | 3.341346 | 1.032082 |
| 3 | 5 | 43 | 318 | 2948 | 4.0 | 2002-08-15 05:28:00 | 0.073483 | 4.415366 | 3.888469 |
| 4 | 5 | 43 | 319 | 207 | 4.0 | 2002-08-15 05:28:47 | 0.073483 | 3.910628 | 1.708250 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 820503 | 330963 | 34 | 53953 | 126 | 0.5 | 2008-12-24 18:52:09 | 0.029359 | 3.361111 | 1.150161 |
| 820504 | 330963 | 34 | 54190 | 74 | 3.0 | 2008-12-24 18:55:15 | 0.176156 | 3.371622 | 0.860718 |
| 820505 | 330963 | 34 | 55069 | 28 | 5.0 | 2008-12-24 18:53:06 | 0.293593 | 3.875000 | 0.552543 |
| 820506 | 330963 | 34 | 55282 | 77 | 5.0 | 2008-12-24 18:52:37 | 0.293593 | 3.298701 | 0.861498 |
| 820507 | 330963 | 34 | 58293 | 91 | 0.5 | 2008-12-24 18:49:18 | 0.029359 | 2.659341 | 0.763192 |
820508 rows × 9 columns
ratings_teste_transformado_modelagem
| userId | Numero_de_Avaliacoes_por_usuarios | movieId | Numero_de_Avaliacoes_por_Filme | rating | timestamp | rating_times | rating_medio_simples | rating_medio_ponderado | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 128 | 19 | 168 | 81 | 3.0 | 1998-07-28 13:16:18 | 0.026308 | 3.166667 | 0.851209 |
| 1 | 128 | 19 | 208 | 196 | 1.0 | 1998-07-28 13:17:36 | 0.008769 | 2.984694 | 1.271296 |
| 2 | 128 | 19 | 356 | 682 | 4.0 | 1998-07-28 13:20:20 | 0.035077 | 4.016862 | 2.716883 |
| 3 | 128 | 19 | 480 | 483 | 2.0 | 1998-07-28 13:14:38 | 0.017539 | 3.628364 | 2.212461 |
| 4 | 128 | 19 | 590 | 304 | 2.0 | 1998-07-28 13:11:21 | 0.017539 | 3.822368 | 1.969439 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 207989 | 330948 | 218 | 115210 | 37 | 1.0 | 2016-01-07 04:58:08 | 0.212142 | 3.891892 | 0.657897 |
| 207990 | 330948 | 218 | 129779 | 2 | 1.5 | 2016-01-07 04:40:17 | 0.318213 | 2.750000 | 0.096609 |
| 207991 | 330948 | 218 | 130634 | 26 | 0.5 | 2016-01-07 04:33:24 | 0.106071 | 3.288462 | 0.448132 |
| 207992 | 330948 | 218 | 132584 | 1 | 0.5 | 2016-01-07 04:58:03 | 0.106071 | 0.500000 | 0.014022 |
| 207993 | 330948 | 218 | 136459 | 1 | 1.5 | 2016-01-07 05:26:23 | 0.318213 | 1.500000 | 0.042067 |
207994 rows × 9 columns
Salvar tabelas¶
# Salvar tabela com dados de treino
#ratings_treino_transformado.to_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/3.Datasets_Transformação/3.1_Datasets_Transformação_parte_1/ratings_treino_transformado.pickle", compression = "gzip")
# Salvar tabela com dados de treino
ratings_treino_transformado_modelagem.to_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/3.Datasets_Transformação/3.1_Datasets_Transformação_parte_1/ratings_treino_transformado_modelagem.pickle", compression = "gzip")
# Salvar tabela com dados de teste
#ratings_teste_transformado.to_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/3.Datasets_Transformação/3.1_Datasets_Transformação_parte_1/ratings_teste_transformado.pickle", compression = "gzip")
# Salvar tabela com dados de teste
ratings_teste_transformado_modelagem.to_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/3.Datasets_Transformação/3.1_Datasets_Transformação_parte_1/ratings_teste_transformado_modelagem.pickle", compression = "gzip")
⚠ Arquivos para as próximas etapas¶
ratings_treino_transformado
ratings_teste_transformado
ratings_treino_transformado_modelagemratings_teste_transformado_modelagem
